﻿using UnityEditor;
using UnityEngine;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System;
using Object = UnityEngine.Object;

namespace CGF
{
    public class AssetBundleBuilder
    {
#if UNITY_EDITOR
        private static bool UseEncrypt = true;
        private static string BuildKey = "GameBigAssets";
#endif

        public struct LineConfig
        {
            public string abName;
            public string resFolder;
        }

        [MenuItem("CGFramework/导出 AssetBundle", priority = -1001)]
        private static void _BuildAllABToApkDir()
        {
            BuildAllAB();
        }

        /// <summary>
        /// 导出所有资源包括代码和配置和预置体
        /// </summary>
        public static void BuildAllAB()
        {
            List<LineConfig> lines = new List<LineConfig>();
            BuildTarget platform = EditorUserBuildSettings.activeBuildTarget;
            string codeTempPath = $"Assets/{Define.BundleFolder}/Code";
            //先执行HybridCLR的编译脚本，再读取。不能直接从ScriptAssemblies里拷贝dll，因为可能会包含不正确的宏开关，导致引用了Editor或者其他代码）
            HybridCLR.Editor.Commands.CompileDllCommand.CompileDll(platform);
            if (HybridCLR.Editor.SettingsUtil.HotUpdateAssemblyFilesExcludePreserved.Count > 0)
            {
                if (!Directory.Exists(codeTempPath)) Directory.CreateDirectory(codeTempPath);
                string hotfixDllSrcDir = HybridCLR.Editor.SettingsUtil.GetHotUpdateDllsOutputDirByTarget(platform);
                foreach (var dll in HybridCLR.Editor.SettingsUtil.HotUpdateAssemblyFilesExcludePreserved)
                {
                    string dllPath = $"{hotfixDllSrcDir}/{dll}";
                    string toPath = $"{codeTempPath}/{dll}.bytes";
                    File.Copy(dllPath, toPath, true);
                    AssetDatabase.ImportAsset(toPath);
                }
            }
            else
            {
                UnityEngine.Debug.LogError("未指定热更程序集(菜单HybridCLR->Settings...)");
            }

            //默认更新的所有资源
            LineConfig line = new LineConfig();
            line.abName = Define.AutoUpdateAbName + Define.AbExtName;
            line.resFolder = Define.BundleFolder;
            lines.Add(line);

            //"AShift+A"里配的按需热更的资源
            for (int i = 0; i < CGFConfig.Instance.extraUpdateFolders.Count; i++)
            {
                Folder folder = CGFConfig.Instance.extraUpdateFolders[i];
                line = new LineConfig();
                line.abName = Path2ABName(folder.path) + Define.AbExtName;
                line.resFolder = folder.path;
                lines.Add(line);
            }

            //开始构建
            BuildAssetBundles(lines, platform);
            AssetDatabase.DeleteAsset(codeTempPath);

            EditorUtility.DisplayDialog("提示", "导出 AssetBundle 完成", "确定");
        }

        /// <summary>
        /// 路径转小写加下划线类型的AB名称,不带后缀名
        /// </summary>
        public static string Path2ABName(string resPath)
        {
            resPath = resPath.Replace("\\", "/").Replace("Assets/", "").Replace(Define.BundleFolder + "/", "");
            resPath = resPath.Replace("/", "_").ToLower();
            return resPath;
        }

        /// <summary>
        /// 将一个目录打包成AB包，并更新list文件的Hash
        /// </summary>
        public static void BuildAssetBundles(List<LineConfig> buildConfigs, BuildTarget platform)
        {
            //临时文件夹用于保留manifest文件，做增量Build,提高效率
            string outputTempDir = Application.dataPath.Replace("Assets", "AssetBundleTemp");

            List<AssetBundleBuild> builds = new List<AssetBundleBuild>();
            for (int i = 0; i < buildConfigs.Count; i++)
            {
                LineConfig config = buildConfigs[i];
                bool isAutoUpdate = config.abName == Define.AutoUpdateAbName + Define.AbExtName;
                string sourceDir = config.resFolder;
                if (string.IsNullOrEmpty(sourceDir))
                {
                    Debug.LogError($"{sourceDir} resFolder 为空");
                    return;
                }

                if (!sourceDir.StartsWith("Assets/")) sourceDir = $"Assets/{sourceDir}";
                List<string> fileList = new List<string>();
                string[] filePaths = Directory.GetFiles(sourceDir, "*", SearchOption.AllDirectories);
                for (int j = 0; j < filePaths.Length; j++)
                {
                    string path = filePaths[j].Replace("\\", "/");
                    if (isAutoUpdate && IsExtraUpdateFolder(path))
                    {
                        continue;
                    }
                    if (path.EndsWith(".meta"))
                    {
                        continue;
                    }
                    path = path.Replace("\\", "/").Replace(Application.dataPath, "Assets");
                    fileList.Add(path);
                }

                AssetBundleBuild build = new AssetBundleBuild();
                build.assetBundleName = config.abName;
                build.assetNames = fileList.ToArray();
                builds.Add(build);
            }

            if (!Directory.Exists(outputTempDir)) Directory.CreateDirectory(outputTempDir);

            string streamingAbFolde = Application.streamingAssetsPath + "/" + Define.StreamingPathAbFolder;
            if (Directory.Exists(streamingAbFolde))
            {
                ClearFolder(streamingAbFolde, true);
            }

            BuildAssetBundleOptions options = BuildAssetBundleOptions.ChunkBasedCompression | BuildAssetBundleOptions.ForceRebuildAssetBundle;
            AssetBundleManifest manifest = BuildPipeline.BuildAssetBundles(outputTempDir, builds.ToArray(), options, platform);

            string resOutputDir = $"{CGFConfig.Instance.ResExportDir}/res_v{CGFConfig.Instance.VersionCode}";
            CopyToExportDir(manifest, outputTempDir, resOutputDir);
            CreateListFile(buildConfigs, outputTempDir, resOutputDir);

            if (Directory.Exists(streamingAbFolde))
            {
                ClearFolder(streamingAbFolde, true);
            }

            if (CGFConfig.Instance.ResStrategy == EResStrategy.StreamingAssetsOnly || CGFConfig.Instance.ResStrategy == EResStrategy.StreamingAssetsAndRemote)
            {
                string autoUpdateAbName = $"{Define.AutoUpdateAbName}{Define.AbExtName}";
                CopyOneFiles($"{resOutputDir}/{autoUpdateAbName}", streamingAbFolde);
                CopyOneFiles($"{resOutputDir}/{Define.HashFileName}", streamingAbFolde);
                if (Define.IsWebGL)
                {
                    string toDir = $"{resOutputDir}/../StreamingAssets/{Define.StreamingPathAbFolder}";
                    CopyOneFiles($"{resOutputDir}/{autoUpdateAbName}", toDir);
                    CopyOneFiles($"{resOutputDir}/{Define.HashFileName}", toDir);
                }
                Debug.Log($"{autoUpdateAbName} 已同时拷贝到 StreamingAssets 目录");
            }

            //如果别的渠道共用同版本的热更资源，直接拷过去
            for (int i = 0; i < CGFConfig.Instance.packs.Length; i++)
            {
                if (i != CGFConfig.Instance.currPackIndex)
                {
                    var pack = CGFConfig.Instance.packs[i];
                    if (pack.useCommonHotfix)
                    {
                        string toDir = $"{CGFConfig.Instance.GetResExportDir(i)}/res_v{pack.versionCode}";
                        CopyAllFiles(resOutputDir, toDir);
                        Debug.Log($"已同时拷贝到 {toDir} 目录");
                    }
                    else
                    {
                        Debug.Log($"{pack.tag} useCommonHotfix=false，未同时拷贝");
                    }
                }
            }

            //打开目录
            Debug.Log($"导出完成: {resOutputDir}");
        }

        private static bool IsExtraUpdateFolder(string path)
        {
            for (int i = 0; i < CGFConfig.Instance.extraUpdateFolders.Count; i++)
            {
                Folder folder = CGFConfig.Instance.extraUpdateFolders[i];
                if (path.Contains(folder.path))
                {
                    return true;
                }
            }
            return false;
        }

        private static void CopyToExportDir(AssetBundleManifest manifest, string outputTempDir, string outputDir)
        {
            string[] abNames = manifest.GetAllAssetBundles();

            if (Directory.Exists(outputDir))
            {
                //删除多余文件
                List<string> oldFiles = new List<string>(Directory.GetFiles(outputDir, $"*{Define.AbExtName}"));
                for (int i = 0; i < abNames.Length; i++)
                {
                    for (int j = 0; j < oldFiles.Count; j++)
                    {
                        if (oldFiles[j].Contains(abNames[i]))
                        {
                            oldFiles.RemoveAt(j);
                            break;
                        }
                    }
                }

                for (int i = 0; i < oldFiles.Count; i++)
                {
                    File.Delete(oldFiles[i]);
                }
            }
            else
            {
                Directory.CreateDirectory(outputDir);
            }


            //开始拷贝
            for (int i = 0; i < abNames.Length; i++)
            {
                string fromPath = $"{outputTempDir}/{abNames[i]}";
                string toPath = $"{outputDir}/{abNames[i]}";
                if (UseEncrypt)
                {
                    byte[] data = File.ReadAllBytes(fromPath);
                    byte[] enData = AESHelper.EncryptBytes(data, MD5Helper.StringToMD5(BuildKey));
                    //添加加密头部标记
                    int headLen = 5;
                    byte[] enDataWithHead = new byte[enData.Length + headLen];
                    for (int j = 0; j < enDataWithHead.Length; j++)
                    {
                        if (j < headLen)
                        {
                            enDataWithHead[j] = 0;
                        }
                        else
                        {
                            enDataWithHead[j] = enData[j - headLen];
                        }
                    }

                    if (File.Exists(toPath) && CompareBytes(File.ReadAllBytes(toPath), enDataWithHead))
                    {
                        continue;
                    }

                    File.WriteAllBytes(toPath, enDataWithHead);
                }
                else
                {
                    if (File.Exists(toPath) && CompareBytes(File.ReadAllBytes(fromPath), File.ReadAllBytes(toPath)))
                    {
                        continue;
                    }

                    File.Copy(fromPath, toPath, true);
                }
            }
        }

        private static void CreateListFile(List<LineConfig> buildConfigs, string outputTempDir, string outputDir)
        {
            StringBuilder stringBuilder = new StringBuilder();
            for (int i = 0; i < buildConfigs.Count; i++)
            {
                LineConfig config = buildConfigs[i];
                string md5 = MD5Helper.FileToMD5(outputTempDir + "/" + config.abName);
                string line = $"{config.abName}|{md5}";
                stringBuilder.AppendLine(line);
            }
            string listFilePath = $"{outputDir}/{Define.HashFileName}";
            File.WriteAllText(listFilePath, stringBuilder.ToString(), new UTF8Encoding(false));
        }

        private static void CopyOneFiles(string fileFullPath, string toDir)
        {
            if (!Directory.Exists(toDir))
            {
                Directory.CreateDirectory(toDir);
            }
            string fileName = Path.GetFileName(fileFullPath);
            File.Copy($"{fileFullPath}", $"{toDir}/{fileName}", true);
            if (toDir.Contains("StreamingAssets"))
            {
                AssetDatabase.Refresh();
            }
        }

        private static void CopyAllFiles(string fromDir, string toDir)
        {
            string[] files = Directory.GetFiles(fromDir, "*", SearchOption.AllDirectories);
            if (!Directory.Exists(toDir))
            {
                Directory.CreateDirectory(toDir);
            }
            for (int i = 0; i < files.Length; i++)
            {
                string fileName = Path.GetFileName(files[i]);
                File.Copy($"{fromDir}/{fileName}", $"{toDir}/{fileName}", true);
            }
            if (toDir.Contains("StreamingAssets"))
            {
                AssetDatabase.Refresh();
            }
        }

        private static bool ClearFolder(string dirPath, bool delRootFolder)
        {
            if (!Directory.Exists(dirPath)) return true;
            try
            {
                //去除文件夹和子文件的只读属性
                DirectoryInfo fileInfo = new DirectoryInfo(dirPath);
                fileInfo.Attributes = FileAttributes.Normal & FileAttributes.Directory;
                File.SetAttributes(dirPath, FileAttributes.Normal);

                //开始递归删除
                if (Directory.Exists(dirPath))
                {
                    foreach (string f in Directory.GetFileSystemEntries(dirPath))
                    {
                        if (File.Exists(f))
                        {
                            File.Delete(f);
                        }
                        else
                        {
                            ClearFolder(f, delRootFolder);
                        }
                    }

                    if (delRootFolder)
                    {
                        AssetDatabase.DeleteAsset(dirPath.Replace(Application.dataPath, "Assets"));
                    }
                }

                return true;
            }
            catch (Exception ex)
            {
                Debug.LogError(ex);
                return false;
            }
        }

        private static bool CompareBytes(byte[] b1, byte[] b2)
        {
            if (b1 == null || b2 == null) return false;
            if (b1.Length != b2.Length) return false;
            for (int i = 0; i < b1.Length; i++)
            {
                if (b1[i] != b2[i]) return false;
            }

            return true;
        }
    }
}
